package com.gitee.wsl.transform.escape.sample

import com.gitee.wsl.transform.escape.Escape
import com.gitee.wsl.transform.escape.EscapeString
import com.gitee.wsl.transform.escape.EscapeException


// Useful resources:
// https://www.ietf.org/rfc/rfc4627.txt
// https://github.com/nst/JSONTestSuite

internal enum class Token {
    LEFT_BRACE,
    RIGHT_BRACE,
    LEFT_BRACKET,
    RIGHT_BRACKET,
    COMMA,
    COLON,
    STRING,
    NUMBER,
    TRUE,
    FALSE,
    NULL,
}

internal val SPECIAL_CHARS = mapOf(
    '"' to '"',
    '\\' to '\\',
    '/' to '/',
    'b' to '\b',
    'f' to '\u000C',
    'n' to '\n',
    'r' to '\r',
    't' to '\t'
)

private val CONTROL_CHARS = (0 until 0x20).map(Int::toChar).toSet()

object JsonEscape : EscapeString() {
    override fun escape(base: String): String {
        var output: StringBuilder? = null
        var i = 0

        fun appendOutput(str: String) {
            output = (output ?: StringBuilder(base.substring(0, i))).append(str)
        }

        while (i < base.length) {
            when (val ch = base.get(i)) {
                '\\' -> appendOutput("""\\""")
                '"' -> appendOutput("""\"""")
                '\n' -> appendOutput("""\n""")
                '\r' -> appendOutput("""\r""")
                '\t' -> appendOutput("""\t""")
                in CONTROL_CHARS -> appendOutput("""\u${ch.code.toString(16).padStart(4, '0')}""")
                else -> output?.append(ch)
            }
            i++
        }
        return output?.toString() ?: base
    }

    override fun unEscape(base: String): String {
        var output: StringBuilder? = null
        val start = 1
        val end = base.length - 1

        var i = start
        while (i < end) {
            val ch = base.get(i)
            if (ch == '\\') {
                output = output ?: StringBuilder(base.substring(start, i))
                when (val escapedChar = base.get(++i)) {
                    in SPECIAL_CHARS -> SPECIAL_CHARS[escapedChar].also { i++ }
                    'u' -> base.substring(i + 1, i + 5).toInt(16).toChar().also { i += 5 }
                    else -> throw EscapeException("Invalid escape character: ${escapedChar}")
                }.let { output.append(it) }
            } else {
                output?.append(ch); i++
            }
        }
        return output?.toString() ?: base.substring(start, end)
    }
}

val Escape.Companion.json get() = JsonEscape